﻿using System;
using System.IO;
using System.Text;
using System.Web;
using System.Web.Services.Protocols;
using System.Xml;
using SoapMessage = System.Web.Services.Protocols.SoapMessage;

namespace SoapLog
{
    public class TraceExtension : SoapExtension
    {
        private Stream _oldStream, _newStream;
        private Type _calleeWebServiceType;
        private DateTime _callingTime;
        private XmlDocument _inputSoapXML, _outputSoapXML;
        private string _clientAddr;
        private string _methodName;

        private static readonly System.Diagnostics.TraceSource errorTraceSource = new System.Diagnostics.TraceSource("SoapLogErrorTraceSource");

        ~TraceExtension()
        {
            if (errorTraceSource != null) { errorTraceSource.Close(); }
        }


        // Save the Stream representing the SOAP request or SOAP response into a local memory buffer.
        public override Stream ChainStream(Stream stream)
        {
            _callingTime = DateTime.Now;
            _oldStream = stream;
            _newStream = new MemoryStream();
            return _newStream;
        }

        // When the SOAP extension is accessed for the first time, the XML Web
        // service method it is applied to is accessed to store the file
        // name passed in, using the corresponding SoapExtensionAttribute.	
        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            return attribute;
        }

        // The SOAP extension was configured to run using a configuration file
        // instead of an attribute applied to a specific XML Web service method.
        public override object GetInitializer(Type WebServiceType)
        {
            return WebServiceType;
        }

        // Receive the file name stored by GetInitializer and store it in a
        // member variable for this specific instance.
        public override void Initialize(object initializer)
        {
            Type theClass = (Type)initializer;
            _calleeWebServiceType = theClass;
        }

        //  If the SoapMessageStage is such that the SoapRequest or
        //  SoapResponse is still in the SOAP format to be sent or received,
        //  save it out to a file.
        public override void ProcessMessage(SoapMessage message)
        {
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeSerialize:
                    break;
                case SoapMessageStage.AfterSerialize:
                    GetOutputSoapXML(message);
                    break;
                case SoapMessageStage.BeforeDeserialize:
                    GetInputSoapXML(message);
                    break;
                case SoapMessageStage.AfterDeserialize:
                    GetInputObjectInfo(message);
                    _clientAddr = GetClientIP();
                    break;
            }
        }

        private void GetInputObjectInfo(SoapMessage message)
        {
            try
            {
                if (message.MethodInfo != null)
                {
                    string temp = message.MethodInfo.Name;
                    _methodName = _calleeWebServiceType.FullName + "." + temp + "()";
                }
            }
            catch (Exception ex)
            {
                LogErrorException(ex);
                throw;
            }
        }

        private void GetOutputSoapXML(SoapMessage message)
        {
            try
            {
                CopyStream(_newStream, _oldStream);
                using (MemoryStream st = new MemoryStream())
                {
                    CopyStream(_newStream, st);
                    st.Position = 0;
                    _outputSoapXML = new XmlDocument();
                    _outputSoapXML.Load(st);
                }
                CreateLog();
            }
            catch (Exception ex)
            {
                LogErrorException(ex);
                throw;
            }

        }

        private void GetInputSoapXML(SoapMessage message)
        {
            try
            {
                CopyStream(_oldStream, _newStream);
                _newStream.Position = 0;
                using (MemoryStream st = new MemoryStream())
                {
                    CopyStream(_oldStream, st);
                    st.Position = 0;
                    _inputSoapXML = new XmlDocument();
                    _inputSoapXML.Load(st);
                }
            }
            catch (Exception ex)
            {
                LogErrorException(ex);
                throw;
            }

        }

        #region util Func.

        private static string GetClientIP()
        {
            HttpRequest req = HttpContext.Current.Request;
            // Look for a proxy address first
            String ip = req.ServerVariables["HTTP_X_FORWARDED_FOR"];
            if (String.IsNullOrEmpty(ip) || ip.ToLower().Contains("unknown"))
            {
                ip = req.ServerVariables["REMOTE_ADDR"];
            }
            else
            {
                try
                {
                    string[] temp = ip.Split(new char[] { ',' });
                    ip = temp[0];
                }
                catch (Exception ex)
                {
                    LogErrorException(ex);
                    ip = "unknown";
                }
            }
            return ip;
        }

        private void CreateLog()
        {
            try
            {
                System.Diagnostics.TraceSource traceSource = new System.Diagnostics.TraceSource("SoapLogTraceSource");
                WriteWebServiceCallAcessLog(traceSource, 1);
                WriteWebServiceCallDetailLog(traceSource, 2);
                traceSource.Flush();
                traceSource.Close();
            }
            catch (Exception ex)
            {
                LogErrorException(ex);
                throw;
            }
        }

        private static void CopyStream(Stream from, Stream to)
        {
            from.Position = 0;
            TextReader reader = new StreamReader(from);
            TextWriter writer = new StreamWriter(to);
            writer.WriteLine(reader.ReadToEnd());
            writer.Flush();
            from.Position = 0;
        }


        // indenet xml string  
        private static String FormatXMLString(String inputXML)
        {
            XmlDocument document = new XmlDocument();
            document.Load(new StringReader(inputXML));
            StringBuilder builder = new StringBuilder();
            using (XmlTextWriter writer = new XmlTextWriter(new StringWriter(builder)))
            {
                writer.Formatting = Formatting.Indented;
                document.Save(writer);
            }
            return builder.ToString();
        }

        #endregion

        #region handle log output

        private void WriteWebServiceCallAcessLog(System.Diagnostics.TraceSource traceSource, int id)
        {
            traceSource.TraceEvent(System.Diagnostics.TraceEventType.Information, id, "== {0} call {1} at {2} ==", _clientAddr, _methodName, _callingTime);
        }

        private void WriteWebServiceCallDetailLog(System.Diagnostics.TraceSource traceSource, int id)
        {
            traceSource.TraceData(System.Diagnostics.TraceEventType.Verbose, id, GetWCFLogCompatibleStr(FormatXMLString(_inputSoapXML.InnerXml), FormatXMLString(_outputSoapXML.InnerXml)));
        }

        private static string GetWCFLogCompatibleStr(string inputSoapXMLStr, string outputSoapXMLStr)
        {
            //note:undone.
            return String.Format("\r\nrequest SOAP:\r\n{0}\r\nresponse SOAP:\r\n{1}", inputSoapXMLStr, outputSoapXMLStr);
        }

        private static void LogErrorException(Exception ex)
        {
            errorTraceSource.TraceData(System.Diagnostics.TraceEventType.Error, 2, ex);
            errorTraceSource.Flush();
        }

        #endregion

    }

    // Create a SoapExtensionAttribute for the SOAP Extension that can be
    // applied to an XML Web service method.
    [AttributeUsage(AttributeTargets.Method)]
    public class TraceExtensionAttribute : SoapExtensionAttribute
    {
        public override Type ExtensionType
        {
            get { return typeof(TraceExtension); }
        }

        public override int Priority { get; set; }
    }
}